package com.openroom.model;

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.apache.log4j.Logger;

import com.jphotomanager.io.ImageImporter;
import com.jphotomanager.io.RawConverter;
import com.openroom.db.DBHelper;
import com.openroom.editing.JPEGImageProcessor;
import com.openroom.event.ApplicationEvent;
import com.openroom.event.EventManager;
import com.openroom.event.EventType;
import com.openroom.model.ModelLocator.ModeFlag.ImageViewMode;
import com.openroom.model.ModelLocator.ModeFlag.NavigationMode;
import com.openroom.model.ModelLocator.ModeFlag.SelectMode;

public class ModelLocator {

    private static final Logger LOGGER = Logger.getLogger(ModelLocator.class);

    public static final int MAX_IMAGE_PER_PAGE = 100;

    private static ModelLocator instance = new ModelLocator();

    private List<Folder> allFolders = new ArrayList<Folder>();

    private Folder currentlySelectedFolder = null;

    private List<Photograph> allPhotographsOfFolder = new ArrayList<Photograph>();

    private List<Photograph> allShowingImage = new ArrayList<Photograph>();

    private Photograph currentlySelectedImage = null;

    private int currentlySelectedIndex;

    private int currentPage = 1;

    private int totalNumOfPagesPossible = 1;

    private int totalImageBeingImported = 0;
    
    private List<Catalog> allCatalogs = new ArrayList<Catalog>();

    private Catalog currentlySelectedCatalog = null;

    private List<Photograph> multiSelectedImages = new ArrayList<Photograph>();

    private ModeFlag currentMode = new ModeFlag();

    private JPEGImageProcessor imageProcessor = null;

    private boolean isDirty = false;

    private ImageHolder currentImageHolder = null;

    private int currentImageWidth = 0;
    
    private int currentImageHeight = 0;
    
    public static class ModeFlag {

        public static enum SelectMode {
            SINGLE, MULTIPLE;
        }

        public static enum ImageViewMode {
            THUMBNAIL, PREVIEW, FULL_VIEW
        }

        public static enum NavigationMode {
            FOLDER, CATALOG;
        }

        public static enum CropMode {
            NO_CROP, CROP_ALLOWED, CROPPING_SELECTION,
        }

        public ImageViewMode currentImageMode = ImageViewMode.THUMBNAIL;

        public NavigationMode navigationMode = NavigationMode.FOLDER;

        public SelectMode currentSelectMode = SelectMode.SINGLE;

        public CropMode cropMode = CropMode.NO_CROP;
    }

    public static ModelLocator getInstance() {
        return instance;
    }

    private ModelLocator() {
    }

    public Photograph getCurrentlySelectedImage() {
        return currentlySelectedImage;
    }

    public void setCurrentlySelectedImage(Photograph currentlySelectedImage) {
        if (null != this.currentlySelectedImage
                && currentlySelectedImage.getPhotoId() != this.currentlySelectedImage
                        .getPhotoId()) {
            imageProcessor = null;
        }

        this.currentlySelectedImage = currentlySelectedImage;
        this.currentlySelectedIndex = allPhotographsOfFolder
                .indexOf(currentlySelectedImage);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_IMAGE_CHANGED));
    }

    public void updateCurrentImage() {
        LOGGER.debug("ModelLocator.updateCurrentImage()");
        currentImageHolder = null;
    }

    public void nextImage() {
        if (isNextAllowed()) {
            if (currentMode.currentImageMode == ImageViewMode.FULL_VIEW)
                setCurrentImageMode(ImageViewMode.PREVIEW);
            setCurrentlySelectedIndex(getCurrentlySelectedIndex() + 1);
            setCurrentlySelectedImage(getAllPhotographsOfFolder().get(
                    getCurrentlySelectedIndex()));
        }
    }

    public boolean isNextAllowed() {
        return getCurrentlySelectedIndex() < getAllPhotographsOfFolder().size() - 1;
    }

    public void prevImage() {
        if (isPreviousAllowed()) {
            if (currentMode.currentImageMode == ImageViewMode.FULL_VIEW)
                setCurrentImageMode(ImageViewMode.PREVIEW);
            setCurrentlySelectedIndex(getCurrentlySelectedIndex() - 1);
            setCurrentlySelectedImage(getAllPhotographsOfFolder().get(
                    getCurrentlySelectedIndex()));
        }
    }

    public boolean isPreviousAllowed() {
        return getCurrentlySelectedIndex() > 0;
    }

    public List<Folder> getAllFolders() {
        return allFolders;
    }

    public void addFolder(Folder folder) {
        getAllFolders().add(folder);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_FOLDER_CHANGED));
    }

    public void removeFolder(Folder folder) {
        getAllFolders().remove(folder);
    }

    public Folder getCurrentlySelectedFolder() {
        return currentlySelectedFolder;
    }

    public void setCurrentlySelectedFolder(Folder currentlySelectedFolder) {
        long start = System.nanoTime();
        this.currentlySelectedFolder = currentlySelectedFolder;
        LOGGER.trace("ModelLocator.setCurrentlySelectedFolder() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_FOLDER_CHANGED));
        LOGGER.trace("ModelLocator.setCurrentlySelectedFolder() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
        setAllPhotographsOfFolder(DBHelper.getInstance()
                .getAllPhotographsOfFolder(
                        currentlySelectedFolder.getFolderId()));
        LOGGER.trace("ModelLocator.setCurrentlySelectedFolder() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
    }

    public List<Photograph> getAllPhotographsOfFolder() {
        return allPhotographsOfFolder;
    }

    public void setAllPhotographsOfFolder(
            List<Photograph> allPhotographsOfFolder) {
        long start = System.nanoTime();
        this.allPhotographsOfFolder = allPhotographsOfFolder;
        this.allShowingImage.clear();
        LOGGER.trace("ModelLocator.setAllPhotographsOfFolder1() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
        if (allPhotographsOfFolder.size() > MAX_IMAGE_PER_PAGE) {
            this.allShowingImage.addAll(allPhotographsOfFolder.subList(0,
                    MAX_IMAGE_PER_PAGE));
            totalNumOfPagesPossible = allPhotographsOfFolder.size()
                    / MAX_IMAGE_PER_PAGE;
            if (allPhotographsOfFolder.size() % MAX_IMAGE_PER_PAGE > 0)
                totalNumOfPagesPossible++;
        } else {
            this.allShowingImage.addAll(allPhotographsOfFolder);
            totalNumOfPagesPossible = 1;
        }
        LOGGER.trace("ModelLocator.setAllPhotographsOfFolder2() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
        setCurrentPage(1);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_IMAGE_CHANGED));
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_SHOWING_IMAGE_CHANGED));
        LOGGER.trace("ModelLocator.setAllPhotographsOfFolder3() finished in "
                + (System.nanoTime() - start) / 1000 + " ms");
        if (!allShowingImage.isEmpty()) {
            setCurrentlySelectedImage(allShowingImage.get(0));
        } else {
            currentlySelectedImage = null;
            currentlySelectedIndex = 0;
            EventManager.getInstance().publishEvent(
                    new ApplicationEvent(EventType.CURRENT_IMAGE_CHANGED));
        }
        long end = System.nanoTime();
        LOGGER.trace("ModelLocator.setAllPhotographsOfFolder() finished in "
                + (end - start) / 1000 + " ms");
    }

    public void deleteCurrentFolder() {
        if (getCurrentMode().navigationMode.equals(NavigationMode.FOLDER)) {
            Folder folder = currentlySelectedFolder;
            Long folderId = folder.getFolderId();
            DBHelper db = DBHelper.getInstance();
            db.deleteFolder(folderId);
            allFolders.remove(folder);
            setAllFolders(allFolders);
            if (!allFolders.isEmpty()) {
                setCurrentlySelectedFolder(allFolders.get(0));
            } else {
                setAllPhotographsOfFolder(new ArrayList<Photograph>());
            }
        } else {
            LOGGER.warn("Delete catalog not supported");
        }
    }

    public int getCurrentlySelectedIndex() {
        return currentlySelectedIndex;
    }

    public void setCurrentlySelectedIndex(int currentlySelectedIndex) {
        this.currentlySelectedIndex = currentlySelectedIndex;
    }

    public List<Photograph> getAllShowingImage() {
        return allShowingImage;
    }

    public void nextPage() {
        if (isNextPageAvailable()) {
            this.allShowingImage.clear();
            this.allShowingImage.addAll(allPhotographsOfFolder.subList(
                    getCurrentPage() * MAX_IMAGE_PER_PAGE, Math.min(
                            (getCurrentPage() + 1) * MAX_IMAGE_PER_PAGE,
                            allPhotographsOfFolder.size())));
            setCurrentPage(getCurrentPage() + 1);
            EventManager.getInstance().publishEvent(
                    new ApplicationEvent(EventType.ALL_SHOWING_IMAGE_CHANGED));
            if (!allShowingImage.isEmpty()) {
                setCurrentlySelectedImage(allShowingImage.get(0));
            } else {
                // Should never be the case
            }
        }
    }

    public boolean isNextPageAvailable() {
        return getCurrentPage() < totalNumOfPagesPossible;
    }

    public void previousPage() {
        if (isPreviousPageAvailable()) {
            this.allShowingImage.clear();
            this.allShowingImage.addAll(allPhotographsOfFolder.subList(Math
                    .max((getCurrentPage() - 2) * MAX_IMAGE_PER_PAGE, 0), Math
                    .min((getCurrentPage() - 1) * MAX_IMAGE_PER_PAGE,
                            allPhotographsOfFolder.size())));
            setCurrentPage(getCurrentPage() - 1);
            EventManager.getInstance().publishEvent(
                    new ApplicationEvent(EventType.ALL_SHOWING_IMAGE_CHANGED));
            if (!allShowingImage.isEmpty()) {
                setCurrentlySelectedImage(allShowingImage.get(0));
            } else {
                // Should never be the case
            }
        }
    }

    public boolean isPreviousPageAvailable() {
        return getCurrentPage() > 1;
    }

    public void changeRating(int ratingValue) {
        currentlySelectedImage.setRatingValue(ratingValue);
        DBHelper.getInstance().changeRating(
                currentlySelectedImage.getPhotoId(), ratingValue);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_RATING_CHANGE));
    }

    public void rejectImage() {
        currentlySelectedImage.setRejected(true);
        DBHelper.getInstance().rejectImage(currentlySelectedImage.getPhotoId());
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_STATUS_CHANGE));
    }

    public void unRejectImage() {
        currentlySelectedImage.setRejected(false);
        DBHelper.getInstance().updateImage(currentlySelectedImage);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_STATUS_CHANGE));
    }

    public void selectImage() {
        currentlySelectedImage.setSelected(true);
        DBHelper.getInstance().updateImage(currentlySelectedImage);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_STATUS_CHANGE));
    }

    public void unSelectImage() {
        currentlySelectedImage.setSelected(false);
        DBHelper.getInstance().updateImage(currentlySelectedImage);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_STATUS_CHANGE));
    }

    public List<Catalog> getAllCatalogs() {
        return allCatalogs;
    }

    public void setAllCatalogs(List<Catalog> allCatalogs) {
        this.allCatalogs = allCatalogs;
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_CATALOG_CHANGED));
    }

    public Catalog getCurrentlySelectedCatalog() {
        return currentlySelectedCatalog;
    }

    public void setCurrentlySelectedCatalog(Catalog currentlySelectedCatalog) {
        this.currentlySelectedCatalog = currentlySelectedCatalog;
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.CURRENT_CATALOG_CHANGED));
        List<Photograph> allPhotographsOfCatalog = DBHelper.getInstance()
                .getAllPhotographsOfCatalog(
                        currentlySelectedCatalog.getCatalogId());
        setAllPhotographsOfFolder(allPhotographsOfCatalog);
    }

    public void setAllFolders(List<Folder> allFolders) {
        this.allFolders = allFolders;
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_FOLDER_CHANGED));
    }

    public void clearWorkspace() {
        this.allShowingImage.clear();
        this.allPhotographsOfFolder.clear();
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.ALL_SHOWING_IMAGE_CHANGED));
    }

    public void importImageToWorkspace(Photograph image) {
        if (this.allShowingImage.size() < MAX_IMAGE_PER_PAGE) {
            this.allShowingImage.add(image);
        }
        this.allPhotographsOfFolder.add(image);
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.IMAGE_IMPORTED));
    }

    public int getImageCounter() {
        return (getCurrentPage() - 1) * MAX_IMAGE_PER_PAGE;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    private void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public void updatePreviewPath(Long imageId, String previewPath) {
        for (Photograph p : allPhotographsOfFolder) {
            if (p.getPhotoId() == imageId) {
                p.setPreviewImagePath(previewPath);
                break;
            }
        }
    }

    public ImageViewMode getCurrentImageMode() {
        return currentMode.currentImageMode;
    }

    public void setCurrentImageMode(ImageViewMode currentMode) {
        this.currentMode.currentImageMode = currentMode;
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.VIEW_MODE_CHANGED));
    }

    public SelectMode getCurrentSelectMode() {
        return getCurrentMode().currentSelectMode;
    }

    public ModeFlag getCurrentMode() {
        return currentMode;
    }

    public void setCurrentSelectMode(SelectMode currentSelectMode) {
        this.getCurrentMode().currentSelectMode = currentSelectMode;
    }

    public void clearMultiSelectImages() {
        getMultiSelectedImages().clear();
    }

    public void addToMultiselectImages(Photograph photograph) {
        if (getMultiSelectedImages().isEmpty()) {
            getMultiSelectedImages().add(currentlySelectedImage);
        }
        getMultiSelectedImages().add(photograph);
        LOGGER.debug(photograph.getFileName());
    }

    public List<Photograph> getMultiSelectedImages() {
        return multiSelectedImages;
    }

    public NavigationMode getNavigationMode() {
        return getCurrentMode().navigationMode;
    }

    public void setNavigationMode(NavigationMode navigationMode) {
        this.getCurrentMode().navigationMode = navigationMode;
        EventManager.getInstance().publishEvent(
                new ApplicationEvent(EventType.VIEW_MODE_CHANGED));
    }

    public void setCurrentMode(ModeFlag currentMode) {
        this.currentMode = currentMode;
    }

    public JPEGImageProcessor getOrLoadImageProcessor() {
        if (imageProcessor == null) {
            String rawFilePath = currentlySelectedImage.getRawFilePath();
            File file = RawConverter.getJpegFileNameForRawFile(new File(
                    rawFilePath));
            try {
                BufferedImage read = ImageIO.read(file);
                imageProcessor = JPEGImageProcessor.createImageProcessor(read);
            } catch (Exception e) {
                throw new RuntimeException("Can not read file " + rawFilePath);
            }
        }
        return imageProcessor;
    }

    public JPEGImageProcessor getOrLoadImagePreviewProcessor() {
        if (imageProcessor == null) {
            try {
                BufferedImage read = ImageIO.read(new File(
                        currentlySelectedImage.getPreviewImagePath()));
                imageProcessor = JPEGImageProcessor.createImageProcessor(read);
                imageProcessor.snapshot();
            } catch (Exception e) {
                throw new RuntimeException("Can not read file "
                        + currentlySelectedImage.getRawFilePath());
            }
        }
        return imageProcessor;
    }

    public boolean isDirty() {
        return isDirty;
    }

    public void setDirty(boolean isDirty) {
        this.isDirty = isDirty;
    }

    public void setCurrentImageHolder(ImageHolder currentImageHolder) {
        this.currentImageHolder = currentImageHolder;
        this.isDirty = true;
    }

    public ImageHolder getCurrentImageHolder() {
        return currentImageHolder;
    }

    public void setImageProcessor(JPEGImageProcessor imageProcessor) {
        this.imageProcessor = imageProcessor;
    }

    public int getCurrentImageWidth() {
        return currentImageWidth;
    }

    public void setCurrentImageWidth(int currentImageWidth) {
        this.currentImageWidth = currentImageWidth;
    }

    public int getCurrentImageHeight() {
        return currentImageHeight;
    }

    public void setCurrentImageHeight(int currentImageHeight) {
        this.currentImageHeight = currentImageHeight;
    }

    public int getTotalImageBeingImported() {
        return totalImageBeingImported;
    }

    public void setTotalImageBeingImported(int totalImageBeingImported) {
        this.totalImageBeingImported = totalImageBeingImported;
    }
}
